#include "../stdlib.hpp"
#include <purewell/code/url.hpp>

namespace purewell
{

PWUrl& gPWUrl(PWUrl::getInstance());

static const uint8_t g_url_table[] = {
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
	1,0,1,1,0,1,1,0, 0,0,0,1,0,0,0,1,
	0,0,0,0,0,0,0,0, 0,0,1,1,1,1,1,1,
	1,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0, 0,0,0,1,1,1,1,0,
	1,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0, 0,0,0,1,1,1,1,1,
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
};

inline
static
char
_toHex(unsigned char i)
{
	if ( i < 10 ) return (char)(i+'0');

	return (char)('A'+(i-10));
}

inline
static
char
_toHalfBin(unsigned char i)
{
	if ( i >= '0' && i <= '9' ) return (char)(i-'0');
	if ( i >= 'A' && i <= 'Z' ) return (char)(i-'A'+10);
	return (char)(i-'a'+10);
}

inline
static
char
_toBin(unsigned char ary[2])
{
	return (_toHalfBin(ary[0]) << 4) | _toHalfBin(ary[1]);
}

inline
static
bool
_isHex(unsigned char i)
{
	if ( i >= '0' && i <= '9' ) return true;
	if ( i >= 'a' && i <= 'f' ) return true;
	if ( i >= 'A' && i <= 'F' ) return true;
	return false;
}

PWUrl&
PWUrl::getInstance(void)
{
	static PWUrl inst;
	return inst;
}

PWUrl::PWUrl()
{
}

size_t
PWUrl::encode(char* obuf, const char* ibuf, size_t iblen) const
{
	const unsigned char* ib((const unsigned char*)ibuf);
	const unsigned char* ie((const unsigned char*)(ibuf+iblen));
	size_t ret(0);
	unsigned char c;

	while ( ib != ie )
	{
		c = *ib;
		if ( g_url_table[c] )
		{
			*obuf = '%';
			++obuf;
			*obuf = _toHex( c >> 4);
			++obuf;
			*obuf = _toHex( c & 0x0f );
			++obuf;
			ret+=3;
		}
		else
		{
			*obuf = c;
			++obuf;
			++ret;
		}

		++ib;
	}

	return ret;
}

std::ostream&
PWUrl::encode(std::ostream& os, std::istream& is) const
{
	unsigned char c(0x00);
	while ( !is.eof() )
	{
		is.get((char&)c);
		if ( !is.good() ) break;
		if ( g_url_table[c] )
		{
			os << '%';
			os << _toHex( c >> 4 );
			os << _toHex( c & 0x0f );
		}
		else
		{
			os << c;
		}
	}

	return os;
}

size_t
PWUrl::decode(char* obuf, const char* ibuf, size_t iblen) const
{
	const unsigned char* ib((const unsigned char*)ibuf);
	const unsigned char* ie((const unsigned char*)(ibuf+iblen));
	unsigned char ary[2] = { 0x00, 0x00 };
	unsigned char c;
	size_t idx(0);
	bool decmode(false);
	size_t ret(0);

	while ( ib != ie )
	{
		c = *ib;
		if ( decmode )
		{
			if ( _isHex(c) )
			{
				ary[idx] = c;
				++idx;

				if ( 2 == idx )
				{
					idx = 0;
					*obuf = _toBin(ary);
					++obuf;
					++ret;
					decmode = false;
				}
			}
			else
			{
				*obuf = '%';
				++obuf;
				++ret;
				if ( 1 == idx )
				{
					*obuf = ary[0];
					++obuf;
					++ret;
				} 
				*obuf = c;
				++obuf;
				++ret;
			}
		}// if ( decmode )
		else
		{
			if ( '%' == c )
			{
				idx = 0;
				decmode = true;
			}
			else
			{
				*obuf = c;
				++obuf;
				++ret;
			}
		}

		++ib;
	}

	return ret;
}

std::ostream&
PWUrl::decode(std::ostream& os, std::istream& is) const
{
	unsigned char ary[2] = { 0x00, 0x00 };
	unsigned char c;
	size_t idx(0);
	bool decmode(false);

	while ( !is.eof() )
	{
		is.get((char&)c);
		if ( !is.good() )
		{
			if ( decmode )
			{
				os << '%';
				if ( idx == 1 ) os << ary[0];
			}

			break;
		}

		if ( decmode )
		{
			if ( _isHex(c) )
			{
				ary[idx] = c;
				++idx;

				if ( 2 == idx )
				{
					idx = 0;
					os << _toBin(ary);
					decmode = false;
				}
			}
			else
			{
				os << '%';
				if ( 1 == idx )
				{
					os << ary[0];
				}

				os << c;

				decmode = false;
			}
		}
		else
		{
			if ( '%' == c )
			{
				idx = 0;
				decmode = true;
			}
			else
			{
				os << c;
			}
		}
	}

	return os;
}

}; // purewell

